#pragma once

#include <vector>
#include <queue>
#include <unordered_map>
#include <functional>
#include <sstream>
#include "TcpLayer.h"
#include <UdpLayer.h>
#include <IcmpLayer.h>
#include "IPv4Layer.h"
#include "PayloadLayer.h"
#include "PacketUtils.h"
#include "SystemUtils.h"
#include <ctime>
#include <fstream>
#include <curl/curl.h>
#include <clickhouse/client.h>

// 最大队列长度（调整以获得最佳速度）
#define MAX_QUEUE_LENGTH 20000

using namespace clickhouse;

// 包统一属性基类,表征了ipv4的IP到传输层大多好用的属性
class unifiedPacketAttr
{
public:
    uint32_t hash_val;  // 五元组计算的流ID
    uint32_t packet_len;     // 包长度
    uint32_t tv_sec;    // 时间戳
    uint32_t tv_nsec;
    std::string src_ip; // 源ip
    std::string dst_ip; // 目的ip

    // ipv4 属性
    uint8_t ttl;         // 生存时间
    uint8_t tos;         // 服务类型
    uint16_t id;         // 标识
    uint16_t offset;     // 偏移
    uint16_t len_ip;      // ip段总长度
    bool ip_version; // ip版本号,0:ipv4,1:ipv6
    uint8_t protocol_ip; // ip数据类型

    // 传输层通用属性
    uint8_t protocol;       // 传输层协议类型tcp,udp,icmp,0,1,2
    uint16_t len_load;      // 负载长度
    uint16_t src_port;      // 源端口,icmp没有
    uint16_t dst_port;      // 目的端口,icmp没有
    uint32_t ack_num;       // 确认号，udp没有
    uint32_t seq_num;  // 序列号，udp没有

    // tcp属性
    uint16_t flag;        // 标志位
    uint16_t window; // 窗口大小

    // udp属性
    uint16_t len_udp;// udp长度

    // icmp属性
    uint8_t icmp_type; // icmp类型
    uint8_t icmp_code; // icmp代码

    unifiedPacketAttr(pcpp::Packet *packet); // 构造函数
    ~unifiedPacketAttr(){};                  // 析构函数
};

// 构造函数
unifiedPacketAttr::unifiedPacketAttr(pcpp::Packet *packet)
{
    // 根据一个包的五元组，计算流ID
    hash_val = pcpp::hash5Tuple(packet);
    // 获取包长度int->uint32_t
    packet_len = static_cast<uint32_t>(packet->getRawPacket()->getRawDataLen());
    // 获取时间戳
    timespec timestamp = packet->getRawPacket()->getPacketTimeStamp();
    tv_sec = timestamp.tv_sec;
    tv_nsec = timestamp.tv_nsec;

    pcpp::IPv4Layer* ipv4layer = packet->getLayerOfType<pcpp::IPv4Layer>();
    // 获取包的五元组，存起来便于检索
    src_ip = ipv4layer->getSrcIPAddress().toString();
    dst_ip = ipv4layer->getDstIPAddress().toString();
    
    // 获取ipv4层属性
    pcpp::iphdr* ipv4hdr = ipv4layer->getIPv4Header();
    tos    = ipv4hdr->typeOfService;
    id     = ipv4hdr->ipId;
    offset = ipv4hdr->fragmentOffset;
    len_ip = ipv4hdr->totalLength;
    ttl    = ipv4hdr->timeToLive;
    protocol_ip = ipv4hdr->protocol;

    if(ipv4hdr->ipVersion==4)
    {
        ip_version = 0;
    }
    else if(ipv4hdr->ipVersion==6)
    {
        ip_version = 1;//其实传入已经判断过了
    }

    if (packet->isPacketOfType(pcpp::TCP))
    {
        protocol = 0;
        pcpp::TcpLayer *tcpLayer = packet->getLayerOfType<pcpp::TcpLayer>();
        pcpp::tcphdr *tcpHeader = tcpLayer->getTcpHeader();
        src_port = packet->getLayerOfType<pcpp::TcpLayer>()->getSrcPort();
        dst_port = packet->getLayerOfType<pcpp::TcpLayer>()->getDstPort();
        flag = 0x00;
        flag = (tcpHeader->finFlag << 7) | (tcpHeader->synFlag << 6) | (tcpHeader->rstFlag << 5) | (tcpHeader->pshFlag << 4) | (tcpHeader->ackFlag << 3) | (tcpHeader->urgFlag << 2) | (tcpHeader->eceFlag << 1) | (tcpHeader->cwrFlag << 0);
        seq_num = tcpHeader->sequenceNumber;
        ack_num = tcpHeader->ackNumber;
        len_load = tcpLayer->getLayerPayloadSize();
        window = tcpHeader->windowSize;
    }
    else if (packet->isPacketOfType(pcpp::UDP))
    {
        src_port = packet->getLayerOfType<pcpp::UdpLayer>()->getSrcPort();
        dst_port = packet->getLayerOfType<pcpp::UdpLayer>()->getDstPort();
        protocol = 1;

        pcpp::UdpLayer *udpLayer = packet->getLayerOfType<pcpp::UdpLayer>();
        pcpp::udphdr *udpHeader = udpLayer->getUdpHeader();
        len_udp = udpHeader->length;
        len_load = udpLayer->getLayerPayloadSize(); // 放入负载长度
    }
    else if (packet->isPacketOfType(pcpp::ICMP))
    {
        // src_port = 0; // no use
        // dst_port = 0;
        protocol = 2;

        pcpp::IcmpLayer *icmpLayer = packet->getLayerOfType<pcpp::IcmpLayer>();
        pcpp::icmphdr *icmpHeader = icmpLayer->getIcmpHeader();

        icmp_code = icmpHeader->code;
        icmp_type = icmpHeader->type;
    }
}